package middleware

import (
	"bytes"
	"github.com/gin-gonic/gin"
	"go-programming-tour-book/blog_service/global"
	"go-programming-tour-book/blog_service/pkg/app"
	"go-programming-tour-book/blog_service/pkg/errcode"
	"go-programming-tour-book/blog_service/pkg/logger"
	"time"
)

type AccessLogWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

// Write 我们在自己定义的 AccessLogWriter 的 Write 中实现了双写
// 因此可以直接通过 AccessLogWriter 的 body 取到值
func (w AccessLogWriter) Write(p []byte) (int, error) {
	//首先尝试将数据写入 w.body
	if n, err := w.body.Write(p); err != nil {
		return n, err
	}
	//然后将数据写入 w.ResponseWriter
	return w.ResponseWriter.Write(p)
}

func AccessLog() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		//初始化 AccessLogWriter
		bodyWriter := &AccessLogWriter{
			ResponseWriter: ctx.Writer,
			body:           bytes.NewBufferString(""),
		}
		//用自己创建的 bodyWriter，替换 gin 框架原有的 ctx.Writer
		ctx.Writer = bodyWriter
		beginTime := time.Now().Unix()
		ctx.Next()
		endTime := time.Now().Unix()

		fields := logger.Fields{
			"request":  ctx.Request.PostForm.Encode(),
			"response": bodyWriter.body.String(),
		}
		//method: 当前的调用方法；request：当前的请求参数；response：当前的请求结果响应主体；
		//status_code：当前的响应结果状态码；begin_time/end_time：调用方法的开始、结束时间。
		s := "access log: method: %s, status_code: %d, begin_time: %d, end_time: %d"
		global.Logger.WithFields(fields).Infof(ctx, s,
			ctx.Request.Method,
			bodyWriter.Status(),
			beginTime,
			endTime,
		)
	}
}

// Recovery 其实 gin 本身已经自带了一个 Recovery 中间件
// 但在项目中，我们需要针对内部情况或生态圈自定义 Recovery 中间件，确保异常在被正常捕获之余能及时地被识别和处理
func Recovery() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		defer func() {
			if err := recover(); err != nil {
				s := "panic recover err: %v"
				global.Logger.WithCallersFrames().Errorf(ctx, s, err)
				app.NewResponse(ctx).ToErrorResponse(errcode.ServerError)
				ctx.Abort()
			}
		}()
		ctx.Next()
	}
}
